Skip to content

工具调用

Function Calling概述

Function Calling(函数调用/工具调用)是Agent实现的关键能力,允许LLM根据用户意图调用外部工具或API。

┌─────────────────────────────────────────────────────────────┐
│                    Function Calling 流程                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  用户: "北京今天天气怎么样?"                                │
│                    │                                        │
│                    ▼                                        │
│  ┌──────────────────────────────────────────────────────┐   │
│  │                     LLM                               │   │
│  │  识别意图 → 需要查询天气 → 生成函数调用请求            │   │
│  └──────────────────────────────────────────────────────┘   │
│                    │                                        │
│                    ▼                                        │
│  LLM输出:                                                     │
│  {                                                             │
│    "tool": "get_weather",                                    │
│    "args": {"city": "北京"}                                  │
│  }                                                           │
│                    │                                        │
│                    ▼                                        │
│  ┌──────────────────────────────────────────────────────┐   │
│  │                   工具执行                            │   │
│  │  调用 weather_api.get_weather(city="北京")            │   │
│  │  返回: {temp: 25, condition: "晴"}                   │   │
│  └──────────────────────────────────────────────────────┘   │
│                    │                                        │
│                    ▼                                        │
│  ┌──────────────────────────────────────────────────────┐   │
│  │                     LLM                               │   │
│  │  整合工具返回 → 生成自然语言回答                       │   │
│  └──────────────────────────────────────────────────────┘   │
│                    │                                        │
│                    ▼                                        │
│  用户: "北京今天天气晴朗,气温25°C"                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

OpenAI Function Calling

基础实现

javascript
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

// 定义可用工具
const tools = [
  {
    type: 'function',
    function: {
      name: 'get_weather',
      description: '获取指定城市的天气信息',
      parameters: {
        type: 'object',
        properties: {
          city: {
            type: 'string',
            description: '城市名称,如"北京"、"上海"'
          },
          unit: {
            type: 'string',
            enum: ['celsius', 'fahrenheit'],
            description: '温度单位'
          }
        },
        required: ['city']
      }
    }
  },
  {
    type: 'function',
    function: {
      name: 'get_stock_price',
      description: '获取股票当前价格',
      parameters: {
        type: 'object',
        properties: {
          symbol: {
            type: 'string',
            description: '股票代码,如"AAPL"、"TSLA"'
          }
        },
        required: ['symbol']
      }
    }
  }
];

// 执行对话
async function chatWithTools(userMessage) {
  const messages = [
    { role: 'user', content: userMessage }
  ];
  
  // 第一次调用:让LLM决定是否调用工具
  const response = await client.chat.completions.create({
    model: 'gpt-4-turbo',
    messages: messages,
    tools: tools,
    tool_choice: 'auto'  // 自动选择工具
  });
  
  const assistantMessage = response.choices[0].message;
  
  // 检查是否有函数调用
  if (assistantMessage.tool_calls) {
    messages.push(assistantMessage);
    
    // 执行所有工具调用
    for (const toolCall of assistantMessage.tool_calls) {
      const toolName = toolCall.function.name;
      const args = JSON.parse(toolCall.function.arguments);
      
      // 调用工具
      const result = await executeTool(toolName, args);
      
      // 添加工具返回
      messages.push({
        role: 'tool',
        tool_call_id: toolCall.id,
        content: JSON.stringify(result)
      });
    }
    
    // 第二次调用:让LLM基于工具结果生成回答
    const finalResponse = await client.chat.completions.create({
      model: 'gpt-4-turbo',
      messages: messages,
      tools: tools
    });
    
    return finalResponse.choices[0].message.content;
  }
  
  return assistantMessage.content;
}

// 工具执行器
async function executeTool(name, args) {
  switch (name) {
    case 'get_weather':
      return await getWeather(args.city, args.unit);
    case 'get_stock_price':
      return await getStockPrice(args.symbol);
    default:
      throw new Error(`Unknown tool: ${name}`);
  }
}

// 工具实现
async function getWeather(city, unit = 'celsius') {
  // 实际项目中调用天气API
  return {
    city: city,
    temperature: unit === 'celsius' ? 25 : 77,
    condition: '晴',
    humidity: 45
  };
}

Python实现

python
from openai import OpenAI

client = OpenAI()

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取天气信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "城市名"},
                    "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
                },
                "required": ["city"]
            }
        }
    }
]

messages = [{"role": "user", "content": "北京今天天气如何?"}]

# 第一次调用
response = client.chat.completions.create(
    model="gpt-4-turbo",
    messages=messages,
    tools=tools,
    tool_choice="auto"
)

assistant_msg = response.choices[0].message

if assistant_msg.tool_calls:
    messages.append(assistant_msg)
    
    for tool_call in assistant_msg.tool_calls:
        tool_name = tool_call.function.name
        args = json.loads(tool_call.function.arguments)
        
        # 执行工具
        result = execute_tool(tool_name, args)
        
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result)
        })
    
    # 第二次调用
    final_response = client.chat.completions.create(
        model="gpt-4-turbo",
        messages=messages,
        tools=tools
    )
    
    print(final_response.choices[0].message.content)

工具设计原则

1. 清晰的描述

javascript
// ❌ 模糊的描述
{
  "name": "search",
  "description": "Search for something"
}

// ✅ 清晰的描述
{
  "name": "search_products",
  "description": "搜索电商平台商品,支持按名称、价格、品牌筛选,返回商品列表及其详细信息"
}

2. 明确的参数

javascript
// ❌ 参数无约束
{
  "parameters": {
    "type": "object",
    "properties": {
      "query": {"type": "string"}
    }
  }
}

// ✅ 参数有约束和说明
{
  "parameters": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "搜索关键词,支持空格分隔多词搜索"
      },
      "category": {
        "type": "string",
        "enum": ["electronics", "clothing", "food", "books"],
        "description": "商品分类"
      },
      "max_price": {
        "type": "number",
        "description": "最高价格,单位元",
        "minimum": 0
      }
    },
    "required": ["query"]
  }
}

3. 返回格式规范

python
def format_tool_response(success: bool, data: Any = None, error: str = None):
    """
    规范工具返回格式
    """
    return {
        "success": success,
        "data": data,
        "error": error,
        "timestamp": datetime.now().isoformat()
    }

# 使用
async def search_products(query, category=None):
    try:
        results = await do_search(query, category)
        return format_tool_response(True, results)
    except Exception as e:
        return format_tool_response(False, error=str(e))

工具类型示例

1. 搜索工具

javascript
const searchTool = {
  type: 'function',
  function: {
    name: 'web_search',
    description: '使用搜索引擎搜索互联网,获取相关信息',
    parameters: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: '搜索关键词'
        },
        num_results: {
          type: 'integer',
          description: '返回结果数量',
          default: 5,
          minimum: 1,
          maximum: 10
        }
      },
      required: ['query']
    }
  }
};

async function webSearch(query, numResults = 5) {
  // 调用搜索API
  const results = await searchAPI.search(query, { num: numResults });
  return results.map(r => ({
    title: r.title,
    url: r.url,
    snippet: r.snippet
  }));
}

2. 数据库查询工具

javascript
const dbQueryTool = {
  type: 'function',
  function: {
    name: 'query_database',
    description: '执行SQL查询,从数据库获取数据',
    parameters: {
      type: 'object',
      properties: {
        sql: {
          type: 'string',
          description: 'SQL SELECT语句,只支持查询操作'
        },
        limit: {
          type: 'integer',
          description: '返回记录数上限',
          default: 100
        }
      },
      required: ['sql']
    }
  }
};

// 安全的数据库查询
async function queryDatabase(sql, limit = 100) {
  // 1. SQL注入检查
  if (!isSafeSQL(sql)) {
    throw new Error('禁止的SQL语句');
  }
  
  // 2. 只允许SELECT
  if (!sql.trim().toUpperCase().startsWith('SELECT')) {
    throw new Error('只支持SELECT查询');
  }
  
  // 3. 添加LIMIT
  if (!sql.includes('LIMIT')) {
    sql = `${sql} LIMIT ${limit}`;
  }
  
  const results = await db.query(sql);
  return results;
}

3. API调用工具

javascript
const apiCallTool = {
  type: 'function',
  function: {
    name: 'call_api',
    description: '调用HTTP API接口获取数据',
    parameters: {
      type: 'object',
      properties: {
        url: {
          type: 'string',
          description: 'API完整URL地址'
        },
        method: {
          type: 'string',
          enum: ['GET', 'POST'],
          default: 'GET'
        },
        headers: {
          type: 'object',
          description: 'HTTP请求头'
        },
        body: {
          type: 'string',
          description: 'POST请求体(JSON字符串)'
        }
      },
      required: ['url']
    }
  }
};

async function callAPI(url, method = 'GET', headers = {}, body = null) {
  const options = {
    method,
    headers: {
      'Content-Type': 'application/json',
      ...headers
    }
  };
  
  if (body && method === 'POST') {
    options.body = body;
  }
  
  const response = await fetch(url, options);
  return await response.json();
}

4. 文件操作工具

javascript
const fileTools = [
  {
    type: 'function',
    function: {
      name: 'read_file',
      description: '读取文件内容',
      parameters: {
        type: 'object',
        properties: {
          path: {
            type: 'string',
            description: '文件路径'
          },
          encoding: {
            type: 'string',
            default: 'utf-8'
          }
        },
        required: ['path']
      }
    }
  },
  {
    type: 'function',
    function: {
      name: 'write_file',
      description: '写入内容到文件',
      parameters: {
        type: 'object',
        properties: {
          path: {
            type: 'string',
            description: '文件路径'
          },
          content: {
            type: 'string',
            description: '文件内容'
          }
        },
        required: ['path', 'content']
      }
    }
  }
];

工具调用最佳实践

1. 错误处理

javascript
async function safeToolExecution(toolName, args) {
  try {
    const result = await executeTool(toolName, args);
    return {
      success: true,
      result
    };
  } catch (error) {
    // 详细记录错误
    console.error(`Tool ${toolName} failed:`, error);
    
    return {
      success: false,
      error: error.message,
      // 可以返回降级结果
      fallback: getFallbackResult(toolName)
    };
  }
}

2. 工具选择策略

javascript
// 指定必须使用的工具
await client.chat.completions.create({
  model: 'gpt-4-turbo',
  messages: messages,
  tools: tools,
  tool_choice: {
    type: 'function',
    function: { name: 'get_weather' }  // 强制使用天气工具
  }
});

// 或让模型自动选择
await client.chat.completions.create({
  model: 'gpt-4-turbo',
  messages: messages,
  tools: tools,
  tool_choice: 'auto'  // 自动选择
});

3. 并行工具调用

javascript
// 支持同时调用多个工具
const response = await client.chat.completions.create({
  model: 'gpt-4-turbo',
  messages: messages,
  tools: tools
});

// 多个工具调用
if (response.choices[0].message.tool_calls.length > 1) {
  // 并行执行
  const promises = response.choices[0].message.tool_calls.map(
    toolCall => executeTool(
      toolCall.function.name,
      JSON.parse(toolCall.function.arguments)
    )
  );
  
  const results = await Promise.all(promises);
}

4. 安全考虑

javascript
class SecureToolExecutor {
  constructor() {
    this.allowedPaths = ['/data/public/', '/data/output/'];
    this.maxFileSize = 10 * 1024 * 1024; // 10MB
  }
  
  validatePath(path) {
    const resolved = path.resolve(path);
    return this.allowedPaths.some(p => resolved.startsWith(p));
  }
  
  async executeFileTool(toolName, args) {
    if (toolName === 'read_file' || toolName === 'write_file') {
      if (!this.validatePath(args.path)) {
        throw new Error('路径不在允许范围内');
      }
    }
    
    return await executeTool(toolName, args);
  }
}

Released under the MIT License.